/*
* SimpConnection.java
*
* Created on 5 Декабрь 2010 г., 13:59
*
* To change this template, choose Tools | Template Manager
* and open the template in the editor.
*/


package protocol.simp;

import com.jcraft.jzlib.*;

import java.io.*;
import java.util.Vector;
import javax.microedition.io.*;
import javax.microedition.lcdui.*;

// #sijapp cond.if modules_ANDROID is "true" #
import android.app.Activity;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
// #sijapp cond.end #
			
import chatty.*;
import chatty.cl.*;
import chatty.ChattyException;
import chatty.chat.message.*;
import chatty.comm.*;
import chatty.modules.*;
import chatty.modules.traffic.*;
import chatty.search.*;
import chatty.util.ResBundle;

import protocol.*;

/**
*
* @author R.U.S.H.A.N.
*/
public class SimpConnection implements Runnable {

	public final  int PROTOCOL_OSCAR				= 0x00000001;
	public final  int PROTOCOL_XMPP 				= 0x00000002;
	public final  int PROTOCOL_MRIM				    = 0x00000003;

	public final  int 
	
	
	PROTOCOL_VER				    = 0x10000000,
	
	P_HEADER_SIZE					= 60,
	
	P_STATUS_OFFLINE				= 0x00000000,
	P_STATUS_ONLINE					= 0x00000001,
	P_STATUS_FFC					= 0x00000002,
	P_STATUS_AWAY					= 0x00000003,
	P_STATUS_DND					= 0x00000004,
	P_STATUS_NA						= 0x00000005,
	P_STATUS_EVIL       			= 0x00000006,
	P_STATUS_DEPRESSION 			= 0x00000007,
	P_STATUS_HOME      				= 0x00000008,
	P_STATUS_WORK       			= 0x00000009,
	P_STATUS_LUNCH      			= 0x0000000A,
	P_STATUS_INVISIBLE				= 0x0000000B, 

	P_STATUS_ALL_CAN_SEE			= 0x00001000,
	P_STATUS_VISLIST_CAN_SEE		= 0x00002000,
	P_STATUS_INVISLIST_CANNOT_SEE	= 0x00003000,
	P_STATUS_CONTACTLIST_CAN_SEE	= 0x00004000,
	P_STATUS_NOBODY_CAN_SEE			= 0x00005000,

	ERROR_BAD_PACKET				= 0x00000001,
	ERROR_INVALID_PROTOCOL			= 0x00001000,
	ERROR_INVALID_PROTOCOL_VER		= 0x00001001,
	ERROR_USER_KICKED				= 0x00001002,
	ERROR_REMOTE_SERVER				= 0x00002000,
	ERROR_DOUBLE_LOGIN				= 0x00002001,
	ERROR_RATE_LIMIT_EXCEEDED		= 0x00002002,
	ERROR_BAD_LOGIN_PW				= 0x00003000,


	SRV_MSG_YOU_WERE_ADDED			= 0x00000001,
	SRV_MSG_AUTH_RESPONSE			= 0x00000002,

	P_SERVER_STATISTICS				= 0x00000001,

	P_ERROR 						= 0x00001000,
	P_PING							= 0x00001001,

	P_LOGIN_HELLO 					= 0x00002000,
	P_LOGIN 						= 0x00002001,
	P_LOGIN_OK 						= 0x00002002,
	P_LOGOUT						= 0x00002003,

	P_MESSAGE						= 0x00003000,
	P_MSG_SEND_URL					= 0x00003001,

	P_MSG_DELIVERY					= 0x00003002,

	P_TYPING_NTF_START 				= 0x00004000,
	P_TYPING_NTF_DONE 				= 0x00004001,

	P_STATUS_CHECK 					= 0x00005000,
	P_STATUS_CHANGE 				= 0x00005001,
	P_XSTATUS_CHANGE 				= 0x00005002,
	P_XTEXT_REQ 					= 0x00005003,
	P_XTEXT_REQ_ANSWER	 			= 0x00005004,
	P_AUTH_REQ 						= 0x00006000,
	P_AUTH_SET 						= 0x00006001,
	P_ROSTER 						= 0x00007000,

	P_USER_INFO		 				= 0x00008000,
	P_USER_INFO_SET 				= 0x00008001,
	P_AVATAR						= 0x00008002,

	P_VERSION	 					= 0x00009000,
	
	P_CONTACT_ADD 					= 0x0000A000,
	P_CONTACT_REMOVE 				= 0x0000A001,
	P_CONTACT_REMOVE_ME 			= 0x0000A002,
	P_CONTACT_MOVE 					= 0x0000A003,
	P_CONTACT_RENAME 				= 0x0000A004,
	P_CONTACT_REMOVED 				= 0x0000A005,
	P_GROUP_ADD 					= 0x0000A006,
	P_GROUP_RENAME 					= 0x0000A007,
	P_GROUP_REMOVE 					= 0x0000A008,
	P_SEARCH 						= 0x0000B000,
	P_SERVER_MESSAGE				= 0x0000C000,
	
	//P_MAGIC                	 		= 0x01DA1EF9,

	P_SUPPORT_DELIVERY				= 0x00000001,
	P_SUPPORT_TYPING				= 0x00000002,
	P_SUPPORT_XSTATUS				= 0x00000004,
	P_NEED_ROSTER					= 0x10000000,
	
	P_AVATAR_TYPE_SMALL				= 0x00000001,	// 24x24 px
	P_AVATAR_TYPE_BIG				= 0x00000002,	// Исходный рамер

	P_SEARCH_ONLY_ONLINE			= 0x00000001;
	
	private StreamConnection sc;
	
	private InputStream is;
    private OutputStream os;
    private ZInputStream zin;
    private ZOutputStream zout;
	
	private Simp simp;
	private SimpPacket pack;
	private Vector outgoing = new Vector();
	private Group activeGroup;
	private SimpContact activeContact;
	private Search search = null;
	private boolean reconnect = false;

	/** Creates a new instance of SimpConnection */
	public SimpConnection(Simp simp, boolean reconnect) {
		this.simp = simp;
		this.reconnect = reconnect;
	}
	
	void disconnect() {
		put( new SimpPacket(P_LOGOUT, 0, simp.getUserId()) );
		simp = null;
		reconnect = false;
		
		try {
				zin.close();
				zout.close();
				sc.close();
			} catch (Exception ex) {
		}
	}
	
	private void send(SimpPacket p) throws ChattyException {
		try {
			byte[] data = p.toByteArray();
			byte[] compressed = new byte[data.length+13];
			byte[] header = new byte[4]; 
			
			int err = zout.compress( data, compressed );
			
			//DebugLog.println( "send: err = " + err);
			
			Util.putDWordLE( header, 0, compressed.length );

			os.write( header );
			os.write( compressed );
			
			/*DebugLog.dump( "send: header = ", header);
			DebugLog.dump( "send: data = ", data);	
			DebugLog.dump( "send: compressed = ", compressed);
			
			
			byte[] all = new byte[compressed.length*10 + 60];
			
			err = zin.uncompress(compressed, all);
			
			DebugLog.println( "send: err = " + err);
			DebugLog.dump( "send: all = ", all);
			*/
			// #sijapp cond.if modules_TRAFFIC is "true" #
			chatty.modules.traffic.Traffic.getInstance().addInTraffic(4 + compressed.length);
			// #sijapp cond.end#
			
		} catch (IOException e) {
			try {
				zin.close();
				zout.close();
			} catch (Exception ex) {
			}
			throw new ChattyException(120, 1);
		}
	}
	private SimpPacket recv() throws ChattyException {
		
		try {
			int r = 0;
			byte[] zlen = new byte[4];	
			
            is.read(zlen, 0, 4);
			
			int dlen_compressed = (int)Util.getDWordLE(zlen, 0);

			if (dlen_compressed == 0xFFFFFFFF) {
				// Compression disable
				byte[] header = new byte[P_HEADER_SIZE];	
				
				int bRead = is.read(header, 0, P_HEADER_SIZE);
				
				int    type = Util.getWordLE(header, 0);
				int    subtype = Util.getWordLE(header, 4);
				int    dlen = (int)Util.getWordLE(header, 56);
				// #sijapp cond.if modules_DEBUGLOG is "true" #
					DebugLog.println( "recv: type = 0x" + Integer.toHexString(type) );
				// #sijapp cond.end #
				
				// #sijapp cond.if modules_TRAFFIC is "true" #
					chatty.modules.traffic.Traffic.getInstance().addInTraffic( 4 + P_HEADER_SIZE + dlen );
				// #sijapp cond.end#
				
				if ( dlen == 0 )
					return new SimpPacket(type, subtype, StringConvertor.byteArray1251ToString(header, 8, 48));
				
				byte[] data = new byte[dlen];
				
				do {
					bRead = is.read(data, r, dlen - r);
					if (-1 == bRead) {
						throw new IOException("EOF");
					} else if (0 == bRead) {
						while (0 == is.available()) {
							try {
								Thread.sleep(100);
							} catch (Exception ex) {
							}
						}
					}
					r += bRead;
				} while ( r < dlen );
				
				return new SimpPacket(type, subtype, StringConvertor.byteArray1251ToString(header, 8, 48), data, dlen);
				
			} else {
				// Compression enable 
				
				// #sijapp cond.if modules_TRAFFIC is "true" #
					chatty.modules.traffic.Traffic.getInstance().addInTraffic( 4 + dlen_compressed );
				// #sijapp cond.end#
					
				byte[] compressed = new byte[dlen_compressed];
				
				do {
					int bRead = is.read(compressed, r, dlen_compressed - r);
					if (-1 == bRead) {
						throw new IOException("EOF");
					} else if (0 == bRead) {
						while (0 == is.available()) {
							try {
								Thread.sleep(100);
							} catch (Exception ex) {
							}
						}
					}
					r += bRead;
				} while ( r < dlen_compressed );
				
				//DebugLog.println( "r = " + r );
				
				byte[] all = new byte[dlen_compressed*10 + 60];
				
				int err = zin.uncompress(compressed, all);
				
				//DebugLog.dump( "recv: compressed = ", compressed);

				int    type = Util.getWordLE(all, 0);
				int    subtype = Util.getWordLE(all, 4);
				int    dlen = Util.getWordLE(all, 56);
				
				/*DebugLog.println( "recv: dlen_compressed = " + dlen_compressed );
				DebugLog.println( "recv: type = 0x" + Integer.toHexString(type) );
				DebugLog.println( "recv: subtype = 0x" + Integer.toHexString(subtype) );
				DebugLog.println( "recv: dlen = " + dlen );
				DebugLog.dump( "recv: all = ", all );
				*/
				byte[] header = new byte[P_HEADER_SIZE];
				System.arraycopy(all, 0, header, 0, P_HEADER_SIZE);
				
				if ( dlen == 0 )
					return new SimpPacket(type, subtype, StringConvertor.byteArray1251ToString(header, 8, 48));
				
				byte[] data = new byte[dlen];
				System.arraycopy(all, P_HEADER_SIZE, data, 0, dlen);
				
				return new SimpPacket(type, subtype, StringConvertor.byteArray1251ToString(header, 8, 48), data, dlen);
			}	
		} catch (IOException e) {
			try {
				zin.close();
				zout.close();
			} catch (Exception ex) {
			}
			throw new ChattyException(120, 1);
		}
	}
	
	public void run() {
		try {
				{	
					simp.setConnectingProgress(0);
					//String server = "simp.dyndns.info";
					
					// #sijapp cond.if modules_DEBUGLOG is "true" #
						String server = "127.0.0.1";
					// #sijapp cond.else #
						String server = "81.176.229.114";
					// #sijapp cond.end#

					try {
						sc = (StreamConnection)Connector.open("socket://" + server + ":3107", Connector.READ_WRITE);
						is = sc.openInputStream();
						os = sc.openOutputStream();
						zin = new ZInputStream(is);
						zout = new ZOutputStream(os, JZlib.Z_DEFAULT_COMPRESSION);
						zin.setFlushMode(JZlib.Z_SYNC_FLUSH);
						zout.setFlushMode(JZlib.Z_SYNC_FLUSH);
						simp.setConnectingProgress(10);
						
						byte [] hello_packet =  new byte[] {
										(byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF, // packet is not packet flag
										(byte)0x00, (byte)0x20, (byte)0x00, (byte)0x00, // PACKET_HEADER_T::type
									// #sijapp cond.if modules_DEBUGLOG is "true" #
										(byte)0x00, (byte)0x00, (byte)0x00, (byte)0x01, // PACKET_HEADER_T::subtype
									// #sijapp cond.else #
										(byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, // PACKET_HEADER_T::subtype
									// #sijapp cond.end#	
										(byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, // PACKET_HEADER_T::login
										(byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00,
										(byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00,(byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00,
										(byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00,(byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00,
										(byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00,(byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00,
										(byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00,(byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00,
										(byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00,(byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00,
										
										(byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, // PACKET_HEADER_T::dlen
										};
						
						os.write(hello_packet);
						simp.setConnectingProgress(20);
						
						SimpPacket	pkt = recv();
						int 		type = pkt.getType();
						int 		subtype = pkt.getSubtype();
						
						if ( P_LOGIN_HELLO == pkt.getType() ) {
					
							if ( pkt.getDlen() > 0 ) {
								
								try {
									zin.close();
									zout.close();
									sc.close();
									
								
									String ip = pkt.get_str( 16 );
									String url = "socket://" + ip + ":1993";
									
									// #sijapp cond.if modules_DEBUGLOG is "true" #
									DebugLog.println( url );
									// #sijapp cond.end#
									sc = (StreamConnection)Connector.open(url, Connector.READ_WRITE);
									
									
									is = sc.openInputStream();
									os = sc.openOutputStream();
									zin = new ZInputStream(is);
									zout = new ZOutputStream(os, JZlib.Z_DEFAULT_COMPRESSION);
									zin.setFlushMode(JZlib.Z_SYNC_FLUSH);
									zout.setFlushMode(JZlib.Z_SYNC_FLUSH);
									
									simp.setConnectingProgress(40);
								} catch (ConnectionNotFoundException e) {
									throw new ChattyException(121, 0);
								} catch (IllegalArgumentException e) {
									throw new ChattyException(122, 0);
								} catch (SecurityException e) {
									throw new ChattyException(123, 9);
								} catch (IOException e) {
									throw new ChattyException(120, 0);
								} catch (Exception e) {
									throw new ChattyException(120, 10);
								}
								
							} else
								throw new ChattyException(130, 0);
							
							simp.setConnectingProgress(50);
							SimpPacket	login = new SimpPacket( P_LOGIN, 0, simp.getUserId() );
							int			protocol_id = 0;
							int 		params = P_SUPPORT_TYPING + P_SUPPORT_XSTATUS;
							
							if ( Options.getBoolean(Options.OPTION_DELIVERY_NOTIFICATION ) )
								params += P_SUPPORT_DELIVERY;
							
							if ( !this.reconnect )
								params += P_NEED_ROSTER;
								
							switch ( simp.getProfile().protocolType ) {
								
								case Profile.PROTOCOL_ICQ:
									protocol_id = PROTOCOL_OSCAR;
									break;
									
								case Profile.PROTOCOL_MRIM:
									protocol_id = PROTOCOL_MRIM;
									break;
									
								case Profile.PROTOCOL_JABBER:
								case Profile.PROTOCOL_FACEBOOK:
								case Profile.PROTOCOL_LJ:
								case Profile.PROTOCOL_VK:
								case Profile.PROTOCOL_QIP:
								case Profile.PROTOCOL_YANDEX:
								case Profile.PROTOCOL_GTALK:
									protocol_id = PROTOCOL_XMPP;
									break;
							}
							login.write_dword( protocol_id );
							login.write_dword( PROTOCOL_VER );
							login.write_dword( params );
							login.write_str( simp.getUserId(), 48 );
							login.write_str( simp.getPassword(), 24 );
							login.write_str( "", 16 ); // host
							login.write_dword( simp.getStatus().getStatusIndex() );
							login.write_str( StringConvertor.notNull(simp.getStatus().getText()), 32 );
							login.write_dword( simp.getXStatus().getStatusIndex() );
							login.write_str( StringConvertor.notNull(simp.getXStatus().getMessage()), 256 );
							
							login.write_str( Integer.toString(Options.getInt(Options.OPTIONS_GMT_OFFSET)), 4 );
							login.write_str( "chatty", 256 );	// caps
							login.write_str( "Chatty IM", 32 );	// name
							login.write_str( chatty.Chatty.VERSION, 32 );	// version
							login.write_str( "###DATE###", 16 );	// build
							login.write_str( chatty.Chatty.microeditionPlatform, 32 );	// os
							login.write_str( chatty.Chatty.getPhone(), 32 );// phone_model

							send(login);
							simp.setConnectingProgress(50);
							
							connection();
						}
			
						
						
					} catch (ConnectionNotFoundException e) {
						throw new ChattyException(121, 0);
					} catch (IllegalArgumentException e) {
						throw new ChattyException(122, 0);
					} catch (SecurityException e) {
						throw new ChattyException(123, 9);
					} catch (IOException e) {
						throw new ChattyException(120, 0);
					} catch (Exception e) {
						throw new ChattyException(120, 10);
					}
				}
		} catch (ChattyException e) {
			Simp o = simp;
			if (null != o) {
				o.processException(e);
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
		disconnect();
	}

	private boolean available() throws ChattyException {
		try {
			return (is.available() > 0);
		} catch (Exception e) {
			throw new ChattyException(120, 11);
		}
	}
	public void connection() throws ChattyException {
		final int defaultPing = 3 * 60000 / 250;
		int ping = defaultPing;
		while (null != simp) {
			SimpPacket packet = null;
			if (0 < outgoing.size()) {
				synchronized (outgoing) {
					packet = (SimpPacket)outgoing.elementAt(0);
					outgoing.removeElementAt(0);
				}
			}
			if (null != packet) {
				send(packet);
				ping = defaultPing;
			} else if ( available() ) {
				try {
					processPacket(recv());
				} catch (ChattyException e) {
					throw e;
				} catch (Exception e) {
				}
				ping = defaultPing;
			} else {
				ping--;
				if (0 == ping) {
					send(new SimpPacket( P_PING, simp.getUserId() ) );
					ping = defaultPing;
				}
				try {
					Thread.sleep(250);
				} catch (Exception e) {
				}
			}
		}
	}
	
	public void processPacket(SimpPacket pkt) throws ChattyException {
		
		if ( pkt == null ) return;
		
		String 		from = pkt.getUserId();
		SimpContact c;	
		int 		type = pkt.getType();
		int 		subtype = pkt.getSubtype();
		
		// #sijapp cond.if modules_DEBUGLOG is "true" #
		DebugLog.println( "type=0x" + Integer.toHexString(type) + " subtype=0x" + Integer.toHexString(subtype) );
		// #sijapp cond.end#
		
		switch( type ) {
			
		case P_ERROR:
			{
				switch( subtype )
				{			
					case ERROR_BAD_PACKET:
						throw new ChattyException(130, 0);

					case ERROR_INVALID_PROTOCOL:
						throw new ChattyException(131, 0);
						
					case ERROR_INVALID_PROTOCOL_VER:
						throw new ChattyException(132, 0);

					case ERROR_USER_KICKED:
						throw new ChattyException(119, 0);

					case ERROR_REMOTE_SERVER:
						throw new ChattyException(124, 0);

					case ERROR_DOUBLE_LOGIN:
						throw new ChattyException(110, 0);

					case ERROR_RATE_LIMIT_EXCEEDED:
						throw new ChattyException(114, 0);

					case ERROR_BAD_LOGIN_PW:
						throw new ChattyException(111, 0);
				}
				break;
			}	

		case P_LOGIN_OK:
			simp.setConnectingProgress( 100 );
			//ContactList.activateContactList();
			break;
			
		case P_LOGOUT:
			//throw new ChattyException(110, 0);
			break;
			
		case P_MESSAGE:
			c = (SimpContact)simp.getItemByUIN(from);
			int messageId = pkt.get_dword();
			int messageType = subtype;
			String messageText = pkt.get_str( 8192 );
			long time = Util.createCurrentDate(false);
			PlainMessage msg = new PlainMessage(from, simp, time, messageText, false);
			simp.addMessage(msg);
			
			if ( Options.getBoolean(Options.OPTION_DELIVERY_NOTIFICATION ) )
				put(new SimpPacket( P_MSG_DELIVERY, messageId, from ));
			
			// #sijapp cond.if modules_ANDROID is "true" #
			/*if(Chatty.isPaused()) {
				NotificationManager mNotificationManager = (NotificationManager)getSystemService(Context.NOTIFICATION_SERVICE); 
				
				int 			icon = android.R.drawable.sym_action_email;
				long 			when = System.currentTimeMillis();
				int 			NOTIFY_ID = messageId;
				CharSequence 	tickerText = "#" + simp.getUnreadMessCount();
				CharSequence 	contentTitle = "new_message_from" + c.getName();
				CharSequence 	contentText =   simp.getUnreadMessCount() + "\n" + messageText;
				
				Notification notification = new Notification(icon, tickerText, when);
				Context context = getApplicationContext(); 
				
				Intent notificationIntent = new Intent(this, NotificationBarActivity.class);
				//PendingIntent contentIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0); // Подробное описание в UPD к статье
				//notification.setLatestEventInfo(context, contentTitle, contentText, contentIntent); // Передаем в наше уведомление параметры вида при развернутой строке состояния
				
				mNotificationManager.notify(NOTIFY_ID, notification); // И наконец показываем наше уведомление через менеджер передав его ID
			}*/
			// #sijapp cond.end #
			break;
			
		case P_MSG_DELIVERY:
			setMessageSended( subtype );
			break;
			
		case P_TYPING_NTF_START:
			c = (SimpContact)simp.getItemByUIN(from);
			if (null != c) {
				simp.beginTyping(c.getUserId(), true);
			}
			break;
			
		case P_TYPING_NTF_DONE:
			c = (SimpContact)simp.getItemByUIN(from);
			if (null != c) {
				simp.beginTyping(c.getUserId(), false);
			}
			break;
			
		case P_STATUS_CHANGE:
			String status_text = "";
			
			status_text = pkt.get_str( 256 );
			
			c = (SimpContact)simp.getItemByUIN(from);
			if (null != c) {
				if ( c.monitor_contact ) {
					c.showPopupWindow(c.getName(), c.getProtocol().getStatusInfo().getName((byte)subtype) );
				}
				if( subtype == P_STATUS_OFFLINE ) {
					c.setOfflineStatus();
				} else {
					if ( c.waiting_for_you ) {
						if ( c.getStatus() == Status.offlineStatus && !c.monitor_contact ){
							c.showPopupWindow(c.getName(), c.getProtocol().getStatusInfo().getName((byte)subtype) );
						}
					}
					c.setStatus((byte)subtype, status_text);
					
					if ( Options.getBoolean(Options.OPTION_SHOW_AVATARS) && c.getAvatar() == null ) {
						c.get_avatar = true;
						put(new SimpPacket(P_AVATAR, P_AVATAR_TYPE_SMALL, c.getUserId() ));
					}
				}
				simp.ui_changeContactStatus(c);
			}
			break;
			
		case P_XSTATUS_CHANGE:
			String xstatus_text = StringConvertor.notNull( pkt.get_str( 256 ) );
			
			c = (SimpContact)simp.getItemByUIN(from);
			if (null != c) {
				c.setXStatus((byte)subtype, xstatus_text);
				simp.ui_changeContactStatus(c);
			}
			break;

		case P_AUTH_REQ:
			simp.addMessage(new SystemNotice(simp, SystemNotice.SYS_NOTICE_AUTHREQ, from, null));
			break;
			
		case P_ROSTER:
			processRoster( pkt );
			break;
			
		case P_USER_INFO:
			{
				UserInfo userInfo = singleUserInfo;
				if (null == userInfo) {
					return;
				}
				from = pkt.getUserId();
				
				userInfo.auth = false;
				//userInfo.uin = from;
				userInfo.nick = pkt.get_str( 32 );
				userInfo.email = pkt.get_str( 32 );
				userInfo.firstName = pkt.get_str( 32 );
				userInfo.lastName = pkt.get_str( 32 );
				userInfo.birthDay = pkt.get_str( 16 );
				
				userInfo.gender = (byte)pkt.get_dword();
				
				userInfo.homeCity = pkt.get_str( 32 );
				userInfo.homeState = pkt.get_str( 32 );
				userInfo.homePage = pkt.get_str( 128 );
				userInfo.homePhones = pkt.get_str( 32 );
				userInfo.homeFax = pkt.get_str( 32 );
				userInfo.homeAddress = pkt.get_str( 256 );
				userInfo.homeTz = pkt.get_str( 4 );
				
				
				userInfo.cellPhone = pkt.get_str( 32 );
				
				userInfo.interests = pkt.get_str( 512 );
				userInfo.about = pkt.get_str( 512 );
				
				userInfo.workCity = pkt.get_str( 32 );
				userInfo.workState = pkt.get_str( 32 );
				userInfo.workPhone = pkt.get_str( 32 );
				userInfo.workFax = pkt.get_str( 32 );
				userInfo.workAddress = pkt.get_str( 256 );
				userInfo.workCompany = pkt.get_str( 32 );
				userInfo.workDepartment = pkt.get_str( 32 );
				userInfo.workPosition = pkt.get_str( 32 );
				userInfo.workWebPage = pkt.get_str( 128 );
				
				userInfo.updateProfileView();
				break;
			}
		
		case P_AVATAR:
			{
				c = (SimpContact)simp.getItemByUIN(from);
				
				
				byte[] avatarBytes = pkt.get_byte_array( pkt.getDlen() );
	
		        if ( avatarBytes != null ) {
					Image avatarImage = Image.createImage( avatarBytes, 0, avatarBytes.length );
					if ( c.get_avatar ) {
						c.setAvatar( avatarImage );
						c.get_avatar = false;
						return;
					}
					UserInfo userInfo = singleUserInfo;
					if (null == userInfo) {
						return;
					}
					userInfo.setAvatar( avatarImage );
					userInfo.updateProfileView();
				}
				break;
			}
		case P_VERSION:
			c = (SimpContact)simp.getItemByUIN(from);
			
			if ( pkt.getDlen() > 256 && c.getVersion ) {
				c.clientCaps = pkt.get_str( 256 );
				c.clientName = pkt.get_str( 32 );
				c.clientVer = pkt.get_str( 32 );
				c.clientBuild = pkt.get_str( 16 );
				c.clientOs = pkt.get_str( 32 );
				c.phoneModel = pkt.get_str( 32 );

				StatusView statusView = ContactList.getInstance().getStatusView();
				
				statusView.init( c, simp );
				
				c.showStatus( statusView );
			} else {
				c.setClient( pkt.get_byte_array(256) );
			
			}
			break;
			
		case P_SERVER_MESSAGE:
		
			switch ( subtype ) {

				case SRV_MSG_YOU_WERE_ADDED:
					simp.addMessage(new SystemNotice(simp, SystemNotice.SYS_NOTICE_YOUWEREADDED, from, null));
					break;
					
				case SRV_MSG_AUTH_RESPONSE:
					int res = pkt.get_dword();
					if( res == 0x00 )
						simp.addMessage(new SystemNotice(simp, SystemNotice.SYS_NOTICE_AUTHDENY, from, null));
					else if ( res == 0x01 )
						simp.addMessage(new SystemNotice(simp, SystemNotice.SYS_NOTICE_GRANDED, from, null));
					break;
			}
			break;
		}
	}

	private void processRoster(SimpPacket packet) {
		
		TemporaryRoster roster = new TemporaryRoster(simp);
		simp.setContactListStub();
		
		while ( !packet.isEof() ) {
			
			String userId = null;
			String name = null;
			String group = null;
			int	type = 0;
			boolean need_auth = true;
			
			userId = packet.get_str( 48 );
			name = packet.get_str( 32 );
			group = packet.get_str( 16 );
			type =  packet.get_dword();
			
			if ( packet.get_dword() == 0 )
				need_auth = false;
			
			int status = packet.get_dword();

			Contact c = roster.makeContact(userId);

			c.setName(name);
			c.setGroup(roster.getOrCreateGroup(group));
			c.setType(type);
			
			c.setBooleanValue(Contact.CONTACT_NO_AUTH, need_auth);
			
			if( simp.getProfile().protocolType == Profile.PROTOCOL_MRIM || simp.getProfile().protocolType == Profile.PROTOCOL_ICQ )
				((SimpContact)c).setStatus((byte)status, "");
			
			roster.addContact(c);
		}
		simp.setContactList(roster.getGroups(), roster.mergeContacts());
	}
	
	protected void removeContact( Contact c ) {
		put(new SimpPacket( P_CONTACT_REMOVE, c.getUserId() ));
	}
	protected void removeGroup( Group g ) {
		SimpPacket packet = new SimpPacket( P_GROUP_REMOVE, simp.getUserId() );
		packet.write_str( g.getName(), 16 );
		put(packet);
	}
	
	protected void moveContact( Contact c, Group g ) {
		SimpPacket packet = new SimpPacket( P_CONTACT_MOVE, c.getUserId() );
		packet.write_str( g.getName(), 16 );
		put(packet);
	}
	
	public void addContact(Contact c) {
		activeContact = (SimpContact)c;
		c.setBooleanValue(Contact.CONTACT_NO_AUTH, true);
		SimpPacket packet = new SimpPacket( P_CONTACT_ADD, c.getUserId() );
		
		packet.write_str( c.getGroup().getName(), 16 );
		put(packet);
	}
	public void addGroup(Group g) {
		activeGroup = g;
		// P_GROUP_ADD
		SimpPacket packet = new SimpPacket( P_GROUP_ADD, simp.getUserId() );
		packet.write_str( g.getName(), 16 );
		put(packet);
	}
	
	public void renameContact(Contact c, String name) {
		// P_CONTACT_RENAME
		SimpPacket packet = new SimpPacket( P_CONTACT_RENAME, c.getUserId() );
		packet.write_str( name, 32 );
		put(packet);
	}
	public void renameGroup(Group g, String name) {
		// P_GROUP_RENAME
		SimpPacket packet = new SimpPacket( P_GROUP_RENAME, simp.getUserId() );
		packet.write_str( g.getName(), 16 );
		packet.write_str( name, 16 );
		put(packet);
	}

	public void removeMe( String userId ) {
		put( new SimpPacket( P_CONTACT_REMOVE_ME, userId ) );
	}
	
	protected void sendTypingNotify(Contact to, boolean isTyping) {

		if( isTyping )
			put( new SimpPacket( P_TYPING_NTF_START, to.getUserId() ) );
		else
			put( new SimpPacket( P_TYPING_NTF_DONE, to.getUserId() ) );
	}
	public void sendAuthReply(String userId, boolean auth) {

		if ( auth == true )
			put( new SimpPacket(P_AUTH_SET, 1, userId) );
		else
			put( new SimpPacket(P_AUTH_SET, 0, userId) );
	}

	public void sendAuthRequest(String userId) {
		put(new SimpPacket(P_AUTH_REQ, userId));
	}
	
	public void sendStatus() {
		Status  status = simp.getStatus();
		
		SimpPacket packet = new SimpPacket( P_STATUS_CHANGE, status.getStatusIndex(), simp.getUserId() );
		packet.write_str(status.getText(), 256);
		
		put( packet );
	}
	public void sendXStatus() {
		XStatus  xstatus = simp.getXStatus();
		
		SimpPacket packet = new SimpPacket( P_XSTATUS_CHANGE, xstatus.getStatusIndex(), simp.getUserId() );
		packet.write_str(xstatus.getMessage(), 256);
		
		put( packet );
	}
	
	private int msgId = 0;
	
	public void sendMessage(PlainMessage message) {
		String to = message.getRcvrUin();
		String msg = message.getText();
		int id = ++msgId;
		message.setMessageId(id);
		
		SimpPacket packet = new SimpPacket(P_MESSAGE, 0, to);
		packet.write_dword(id);
		packet.write_str(msg, msg.length());
		put(packet);
		
		if (true) {
			messages.addElement(message);
			setMessageSended(-1);
		}
	}

	public void getClientInfo(String userId) {
		SimpPacket packet = new SimpPacket(P_VERSION, userId);
		put(packet);
	}
	
	private UserInfo singleUserInfo;
	
	public UserInfo getUserInfo(SimpContact contact) {
		UserInfo info = new UserInfo(simp, contact.getUserId());
		SimpPacket packet = new SimpPacket(P_USER_INFO, contact.getUserId() );
		put(packet);
		
		info.uin = contact.getUserId();
		singleUserInfo = info;
		return info;
	}
	
	public void getAvatar(UserInfo info) {
		put(new SimpPacket(P_AVATAR, P_AVATAR_TYPE_BIG, info.uin));
		singleUserInfo = info;
    }
	
	public void saveUserInfo(UserInfo info) {

		SimpPacket pkt = new SimpPacket(P_USER_INFO_SET, info.realUin );
		
		pkt.write_str( info.nick, 32 );
		pkt.write_str( info.email, 32 );
		pkt.write_str( info.firstName, 32 );
		pkt.write_str( info.lastName, 32 );
		pkt.write_str( info.birthDay, 16 );
		
		pkt.write_dword( info.gender );

		pkt.write_str( info.homeCity, 32 );
		pkt.write_str( info.homeState, 32 );
		pkt.write_str( info.homePage, 128 );
		pkt.write_str( info.homePhones, 32 );
		pkt.write_str( info.homeFax, 32 );
		pkt.write_str( info.homeAddress, 256 );
		pkt.write_str( info.homeTz, 4 );


		pkt.write_str( info.cellPhone, 32 );

		pkt.write_str( info.interests, 512 );
		pkt.write_str( info.about, 512 );

		pkt.write_str( info.workCity, 32 );
		pkt.write_str( info.workState, 32 );
		pkt.write_str( info.workPhone, 32 );
		pkt.write_str( info.workFax, 32 );
		pkt.write_str( info.workAddress, 256 );
		pkt.write_str( info.workCompany, 32 );
		pkt.write_str( info.workDepartment, 32 );
		pkt.write_str( info.workPosition, 32 );
		pkt.write_str( info.workWebPage, 128 );
		
		put(pkt);
	}

	void searchUsers(Search cont) {
		this.search = cont;
		SimpPacket packet = new SimpPacket(P_SEARCH, "" );
		packet.write_str( cont.getSearchParam(Search.UIN), 48 );
		packet.write_str( cont.getSearchParam(Search.EMAIL), 32 );
		packet.write_str( cont.getSearchParam(Search.NICK), 32 );
		packet.write_str( cont.getSearchParam(Search.FIRST_NAME), 32 );
		packet.write_str( cont.getSearchParam(Search.LAST_NAME), 32 );
		packet.write_str( cont.getSearchParam(Search.CITY), 32 );
		
		int g = Util.strToIntDef(cont.getSearchParam(Search.GENDER), 0);
		if (0 < g) {
			packet.write_dword( g );
		}
		if ("1".equals(cont.getSearchParam(Search.ONLY_ONLINE))) {
			packet.write_dword( P_SEARCH_ONLY_ONLINE );
		}

		/*String[] age = Util.explode(cont.getSearchParam(Search.AGE), '-');
		if (2 == age.length) {
			if (0 < Util.strToIntDef(age[0], 0)) {
				packet.writeWtld_byte(0x000A, Util.strToIntDef(age[0], 1));
			}
			if (Util.strToIntDef(age[1], 100) < 99) {
				packet.writeWtld_byte(0x000B, Util.strToIntDef(age[1], 99));
			}
		}*/
		//DebugLog.dump("searchUsers", packet.toByteArray(0));
		put(packet);
	}


	private void put(SimpPacket packet) {
		synchronized (outgoing) {
			outgoing.addElement(packet);
		}
	}

    private Vector messages = new Vector();
    private void setMessageSended(long msgId) {
        PlainMessage msg = null;
        for (int i = 0; i < messages.size(); ++i) {
            PlainMessage m = (PlainMessage)messages.elementAt(i);
            if (m.getMessageId() == msgId) {
                msg = m;
                break;
            }
        }
        if (null != msg) {
            msg.setSendingState(PlainMessage.NOTIFY_FROM_CLIENT);
            messages.removeElement(msg);
        }
        
        long date = Util.createCurrentDate(false) - 5 * 60;
        for (int i = messages.size() - 1; i >= 0; --i) {
            PlainMessage m = (PlainMessage)messages.elementAt(i);
            if (date > m.getNewDate()) {
                messages.removeElement(m);
            }
        }
    }
}